import {observe} from "./observe/index.js";
import Watcher, {nextTick} from "./observe/watcher.js";
import Dep from "./observe/dep.js";

export function initStatus(vm){
    // vm是Vue实例
    const opt = vm.$options
    // 处理data属性
    if(opt.data){
        initData(vm)
    }
    // 处理computed计算属性
    if(opt.computed){
        initComputed(vm)
    }
    // 处理watch方法
    if(opt.watch){
        initWatch(vm)
    }
}

// 对vm进行二次代理，可以让用户通过vm.xxx的方式直接读取到data中的值
function proxy(target,key){
    for (const dataKey in target[key]) {
        Object.defineProperty(target, dataKey,{
            get(){
                return target[key][dataKey]
            },
            set(newValue){
                target[key][dataKey] = newValue
            }
        })
    }
}

function initData(vm){
    let data = vm.$options.data
    data = typeof data === "function" ? data.call(vm) : data
    // 拿到数据开始进行数据劫持，把数据变成响应式的
    observe(data)
    // 吧data方法返回的对象挂载到Vue的实例上
    vm._data = data
    // 目前取值必须通过 vm._data.xxx 的方式来获取值或者设置值
    // 如果想直接通过 vm.xxx 的方式来设置值，则必须对vm再进行一次代理
    proxy(vm,"_data")
}

function initComputed(vm){
    let computed = vm.$options.computed
    const computedWatchers = vm._computedWatchers = {}
    // 遍历计算属性中的每一个方法，将方法名作为一个key
    Object.keys(computed).forEach(key=>{
        let getter = typeof computed[key] === "function" ? computed[key] : computed[key].get
        // 给每个计算属性绑定一个watcher，并且标记状态是lazy
        // 然后再watcher中判断这个状态，决定是否立即执行一次和是否返回缓存的数据
        computedWatchers[key] = new Watcher(vm,getter,{lazy:true})
        // 劫持每一个属性，将key作为一个新的属性，方法的返回值作为这个属性值添加到vm上
        defineComputed(vm,key,computed)
    })
}

function defineComputed(target,key,computed){
    let setter = computed[key].set || (()=>{})
    Object.defineProperty(target,key,{
        get:createComputedGetter(key),
        set:setter
    })
}

// 收集计算属性watcher
function createComputedGetter(key){
    return function (){
        let watcher = this._computedWatchers[key]
        // 这里的dirty默认是true
        if(watcher.dirty){
            // 调用完watcher上的evaluate方法后，会吧这个dirty改成false
            // 同时吧计算属性的方法返回值赋值给当前watcher的value属性上
            watcher.evaluate()
        }
        // 判断Dep.target是否还有值，有值就表示计算属性的watcher出栈后，还有一个渲染watcher
        if(Dep.target){
            // 调用watcher中的depend方法，获取计算属性watcher所有的属性，也就是每一个dep
            // 遍历这些dep，去同时吧渲染watcher添加到这些计算属性所依赖的dep的订阅者中
            // 这样当这些依赖的值发生变化时，就会通知到渲染watcher，从而去更新页面
            watcher.depend()
        }
        // 这样在多次调用计算属性的get方法时，只会触发一次真正的get方法
        return watcher.value
    }
}


function initWatch(vm){
    // 从vm中获取用户定义的watch对象
    let watch = vm.$options.watch
    // 遍历这个对象获取每一个属性名和属性值
    for (const watchKey in watch) {
        // 属性值
        let handle = watch[watchKey]
        // 属性值可能是一个数组
        /**
         age:[
         (newVal,oldVal)=>{
                    console.log(newVal,oldVal)
                },
         (newVal,oldVal)=>{
                    console.log(newVal,oldVal)
                },
         ]
         */
        if(Array.isArray(handle)){
            for (let handleElement of handle) {
                createWatcher(vm,watchKey,handleElement)
            }
        }else{
            // 如果不是数组可能是一个字符串或者是一个回调
            // 这里先不考虑是字符串的情况
            createWatcher(vm,watchKey,handle)
        }
    }

}

function createWatcher(vm,keyOrFn,handle){
  vm.$watch(keyOrFn,handle)
}

export function initStateMixin(Vue){
    Vue.prototype.$nextTick = nextTick
    Vue.prototype.$watch = function (expOrFn, cb) {
        new Watcher(this, expOrFn, {user:true},cb)
    }
}